#ifndef     __PROTO_RECV_DRV_H
#define     __PROTO_RECV_DRV_H

#include "../conn/frame.h"
#include "../utils/byte_view.h"
#include "../utils/byte_alloc.h"
// #include "../utils/log.h"
#include <string.h>

namespace pub_driver {

template <typename MQ, typename RX_CHAN, uint32_t BUFF_SIZE>
struct UartDmaRecvBuff {

    UartDmaRecvBuff(MQ &mq) : m_mq(mq) {}

    void on_recv_idle() {
        using namespace conn;

        uint32_t index = BUFF_SIZE - RX_CHAN::ndtr();
        if (index == m_index) {
            return;
        }
        if (index > m_index) {
            m_mq.post( ByteView(buf + m_index, index - m_index) );
        } else {
            m_mq.post( ByteView(buf + m_index, BUFF_SIZE - m_index) );
            if (index != 0) {
                m_mq.post( ByteView(buf, index) );
            }
        }
        m_index = index;
    }

    uint8_t buf[BUFF_SIZE];

private:
	MQ &m_mq;
    uint32_t m_index = 0;
};

template <typename MQ, uint32_t BYTE_DELAY_MS = 10, uint32_t BYTE_BUF_SIZE = 1024>
class ProtoRecvDrv {

public:
    ProtoRecvDrv(MQ& mq) : m_mq(mq) {}

    void recv_next(uint8_t len) {
        m_alloc.next(len);
    }

    void recv_from(conn::Frame &frame) {

        for (;;) {
            uint8_t len;
            wait();
            if (!sync(len)) {
             //   println("sync fail");
                continue;
            }
            if (len < 8) {
             //   println("len fail:%d", len);
                continue;
            }

            uint8_t *buf = m_alloc.alloc(len);
            buf[0] = conn::proto::HEAD0;
            buf[1] = conn::proto::HEAD1;
            buf[2] = len;

            if (!take(buf + 3, len - 3)) {
             //   println("take fail ");
                continue;
            }

            uint8_t sum = alg::xor_sum(buf + 3, len - 5);
            if (sum != buf[len - 2]) {
            //    println("check sum fail");
                continue;
            }

            if (conn::proto::END != buf[len - 1]) {
             //   println("end fail");
                continue;
            }

            frame.buf = buf;
            frame.len = len;
            return;
        }
    }

private:
    bool sync(uint8_t &len) {

        uint8_t tmp;
        bool flag = false;
        for (;;) {
            if (!take_byte(tmp)) {
                return false;
            }
            if (flag && (tmp == conn::proto::HEAD1)) {
                break;
            }
            flag = tmp == conn::proto::HEAD0;
        }
        return take_byte(len);
    }

    bool take(uint8_t *buf, uint8_t len) {

        uint8_t index = 0;
        while (index < len) {
            if ((m_bw.len <= 0) && (m_mq.poll(m_bw, BYTE_DELAY_MS) != osOK)) {
                return false;
            }
            uint8_t n = len - index;
            if (m_bw.len < n) {
                n = m_bw.len;
            }
            memcpy(buf + index, m_bw.buf, n);
            index += n;
            m_bw.buf += n;
            m_bw.len -= n;
        }
        return true;
    }

    bool take_byte(uint8_t &value) {
        if ((m_bw.len <= 0) && (m_mq.poll(m_bw, BYTE_DELAY_MS) != osOK)) {
            return false;
        }
        value = *m_bw.buf;
        m_bw.buf += 1;
        m_bw.len -= 1;
        return true;
    }

    void wait() {
        if (m_bw.len == 0) {
            m_mq.poll(m_bw);
        }
    }


private:
    MQ &m_mq;
    ByteView m_bw;
    ByteAlloc<BYTE_BUF_SIZE> m_alloc;
};

}

#endif
